using System;
using System.Collections.Generic;
using System.Text;

namespace SqlTools.CodeGenerator.CSharp
{
	public enum AccessibilityLevel
	{
		Public,
		Protected,
		Internal,
		ProtectedInternal,
		Private
	}

	public enum OverloadType
	{
		None,
		New,
		Override,
		Abstract
	}

	/// <summary>
	/// Class used for clean, formatted generation of c# code;
	/// </summary>
	public class CodeBuilder
	{
		protected StringBuilder _sb;

		protected List<string> _namespaces;
		protected List<string> _bases;
		
		protected string _namespace;
		protected string _className;
		protected bool _partialClass;
		protected AccessibilityLevel _classAccessibility;

		protected int _indent;
		protected bool _singleLineIndent;

		protected bool _newline;

		protected AccessibilityLevel _defaultFieldAccessibility;
		protected AccessibilityLevel _defaultPropertyAccessibility;
		protected AccessibilityLevel _defaultConstructorAccessibility;
		protected AccessibilityLevel _defaultMethodAccessbility;

		public CodeBuilder(string @namespace, string className)
		{
			_sb = new StringBuilder();

			_namespaces = new List<string>();
			_bases = new List<string>();

			_namespace = @namespace;
			_className = className;
			_partialClass = false;
			_classAccessibility = AccessibilityLevel.Public;

			_indent = 2;	// starting at 2 since we are already in the class

			_singleLineIndent = false;
			_newline = true;	// starting off, we are always on a new line

			_defaultConstructorAccessibility = AccessibilityLevel.Public;
			_defaultFieldAccessibility = AccessibilityLevel.Protected;
			_defaultMethodAccessbility = AccessibilityLevel.Public;
			_defaultPropertyAccessibility = AccessibilityLevel.Public;
		}

		public int CurrentIndent
		{
			get { return _indent; }
		}

		public List<string> Namespaces
		{
			get { return _namespaces; }
		}

		public List<string> Bases
		{
			get { return _bases; }
		}

		public string ClassName
		{
			get { return _className; }
		}

		public string Namespace
		{
			get { return _namespace; }
		}

		public bool PartialClass
		{
			get { return _partialClass; }
			set { _partialClass = value; }
		}

		public AccessibilityLevel ClassAccessibility
		{
			get { return _classAccessibility; }
			set { _classAccessibility = value; }
		}

		public AccessibilityLevel DefaultConstructorAccessibility
		{
			get { return _defaultConstructorAccessibility; }
			set { _defaultConstructorAccessibility = value; }
		}

		public AccessibilityLevel DefaultFieldAccessibility
		{
			get { return _defaultFieldAccessibility; }
			set { _defaultFieldAccessibility = value; }
		}

		public AccessibilityLevel DefaultPropertyAccessbility
		{
			get { return _defaultPropertyAccessibility; }
			set { _defaultPropertyAccessibility = value; }
		}

		public AccessibilityLevel DefaultMethodAccessbility
		{
			get { return _defaultMethodAccessbility; }
			set { _defaultMethodAccessbility = value; }
		}

		public CodeBuilder Append(string text)
		{
			if ( _newline )
			{
				AppendIndent();
				_newline = false;
			}

			_sb.Append(text);

			return this;
		}

		public CodeBuilder BeginBlock()
		{
			Append("{");
			NewLine();

			Indent();

			return this;
		}

		public CodeBuilder EndBlock()
		{
			Outdent();

			Append("}");
			NewLine();

			return this;
		}

		public CodeBuilder BeginRegion(string regionName)
		{
			Append("#region ");
			Append(regionName);
			NewLine();

			return this;
		}

		public CodeBuilder EndRegion()
		{
			Append("#endregion");
			NewLine();

			return this;
		}

		public CodeBuilder AppendField(string type, string fieldName)
		{
			return AppendField(_defaultFieldAccessibility, type, fieldName);
		}

		public CodeBuilder AppendField(AccessibilityLevel level, string type, string fieldName)
		{
			Append(GetAccessibilityLevel(level));
			Append(" ");
			Append(type);
			Append(" ");
			Append(fieldName);
			Append(";");
			NewLine();

			return this;
		}

		public CodeBuilder BeginConstructor()
		{
			return BeginConstructor(_defaultConstructorAccessibility, null);
		}

		public CodeBuilder BeginConstructor(AccessibilityLevel level)
		{
			return BeginConstructor(level, null);
		}

		public CodeBuilder BeginConstructor(AccessibilityLevel level, Dictionary<string, string> parameters)
		{
			Append(GetAccessibilityLevel(level));
			Append(" ");
			Append(_className);
			Append("(");

			if ( parameters != null )
			{
				AppendParameters(parameters);
			}

			Append(")");
			NewLine();
			BeginBlock();

			return this;
		}

		public CodeBuilder EndConstructor()
		{
			EndBlock();

			return this;
		}

		public CodeBuilder BeginMethod(string returnType, string name)
		{
			return BeginMethod(_defaultMethodAccessbility, returnType, name, null);
		}

		public CodeBuilder BeginMethod(string returnType, string name, Dictionary<string, string> parameters)
		{
			return BeginMethod(_defaultMethodAccessbility, returnType, name, parameters);
		}

		public CodeBuilder BeginMethod(AccessibilityLevel level, string returnType, string name)
		{
			return BeginMethod(level, returnType, name, null);
		}

		public CodeBuilder BeginMethod(AccessibilityLevel level, string returnType, string name, Dictionary<string, string> parameters)
		{
			return BeginMethod(level, returnType, name, parameters, OverloadType.None);
		}

		public CodeBuilder BeginMethod(AccessibilityLevel level, string returnType, string name, Dictionary<string, string> parameters, OverloadType overload)
		{
			return BeginMethod(level, returnType, name, parameters, overload, false);
		}

		public CodeBuilder BeginMethod(AccessibilityLevel level, string returnType, string name, Dictionary<string, string> parameters, OverloadType overload, bool isStatic)
		{
			Append(GetAccessibilityLevel(level));
			Append(" ");
			if ( overload == OverloadType.New )
			{
				Append("new ");
			}
			else if ( overload == OverloadType.Override )
			{
				Append("override ");
			}

			if ( isStatic )
				Append("static ");

			Append(returnType);
			Append(" ");
			Append(name);
			Append("(");
			if ( parameters != null )
			{
				AppendParameters(parameters);
			}

			Append(")");
			NewLine();
			BeginBlock();

			return this;
		}

		public CodeBuilder EndMethod()
		{
			EndBlock();

			return this;
		}

		public CodeBuilder BeginProperty(string type, string name)
		{
			return BeginProperty(_defaultPropertyAccessibility, type, name);
		}

		public CodeBuilder BeginProperty(AccessibilityLevel level, string type, string name)
		{
			return BeginProperty(level, type, name, OverloadType.None);
		}

		public CodeBuilder BeginProperty(AccessibilityLevel level, string type, string name, OverloadType overload)
		{
			return BeginProperty(level, type, name, overload, false);
		}

		public CodeBuilder BeginProperty(AccessibilityLevel level, string type, string name, OverloadType overload, bool isStatic)
		{
			Append(GetAccessibilityLevel(level));
			Append(" ");
			if ( overload == OverloadType.New )
			{
				Append("new ");
			}
			else if ( overload == OverloadType.Override )
			{
				Append("override ");
			}

			if ( isStatic )
				Append("static ");

			Append(type);
			Append(" ");
			Append(name);
			NewLine();
			BeginBlock();

			return this;
		}

		public CodeBuilder EndProperty()
		{
			EndBlock();

			return this;
		}

		public CodeBuilder BeginGet()
		{
			Append("get");
			NewLine();
			BeginBlock();

			return this;
		}

		public CodeBuilder EndGet()
		{
			EndBlock();

			return this;
		}

		public CodeBuilder AppendGet(string propertyName)
		{
			Append("get { return ");
			Append(propertyName);
			Append("; }");
			NewLine();

			return this;
		}

		public CodeBuilder BeginSet()
		{
			Append("set");
			NewLine();
			BeginBlock();

			return this;
		}

		public CodeBuilder EndSet()
		{
			EndBlock();

			return this;
		}

		public CodeBuilder Indent()
		{
			_indent++;

			return this;
		}

		public CodeBuilder SingleLineIndent()
		{
			Indent();
			_singleLineIndent = true;

			return this;
		}

		public CodeBuilder Outdent()
		{
			_indent--;

			return this;
		}

		public CodeBuilder NewLine()
		{
			return NewLine(false);
		}

		public CodeBuilder NewLine(bool closeStatement)
		{
			if ( closeStatement )
				_sb.Append(";");

			_sb.AppendLine();
			_newline = true;

			if ( _singleLineIndent )
				Outdent();

			_singleLineIndent = false;

			return this;
		}

		public CodeBuilder AppendParameters(Dictionary<string, string> parameters)
		{
			bool first = true;
			foreach ( KeyValuePair<string, string> kvp in parameters )
			{
				if ( !first )
				{
					Append(", ");
				}

				first = false;

				Append(kvp.Key);
				Append(" ");
				Append(kvp.Value);
			}

			return this;
		}

		public string GetText()
		{
			StringBuilder sb = new StringBuilder();

			foreach ( string ns in _namespaces )
			{
				sb.Append("using ").Append(ns).AppendLine(";");
			}

			sb.AppendLine();

			sb.Append("namespace ").AppendLine(_namespace);
			sb.AppendLine("{");

			sb.Append("\t").Append(GetAccessibilityLevel(_classAccessibility));
			sb.Append(" ");
			if ( _partialClass )
				sb.Append("partial ");

			sb.Append("class ");

			sb.Append(_className);

			if ( _bases.Count > 0 )
			{
				sb.Append(" : ");

				bool first = true;
				foreach ( string bas in _bases )
				{
					if ( !first )
						sb.Append(", ");

					first = false;

					sb.Append(bas);
				}
			}

			sb.AppendLine();
			sb.AppendLine("\t{");	// open class

			sb.Append(_sb.ToString());

			sb.AppendLine("\t}"); // close class
			sb.AppendLine("}");

			return sb.ToString();
		}

		protected void AppendIndent()
		{
			_sb.Append(new string('\t', _indent));
		}

		protected string GetAccessibilityLevel(AccessibilityLevel level)
		{
			switch ( level )
			{
				case AccessibilityLevel.Public:
					return "public";
				case AccessibilityLevel.Protected:
					return "protected";
				case AccessibilityLevel.Internal:
					return "internal";
				case AccessibilityLevel.ProtectedInternal:
					return "protected internal";
				case AccessibilityLevel.Private:
					return "private";
			}

			return "";
		}
	}
}
